#include "gerber.h"
#include "job.h"
#include "filereader.h"
#include "processor.h"
#include "command.h"
#include "object.h"
#include "graphicsscene.h"
#include "graphicsitem.h"

#include <QDebug>
#include <QElapsedTimer>
#include <QFile>
#include <QGraphicsScene>
#include <QImage>
#include <QPainter>

namespace LeGerber
{

	Job::Job(const QString &inputFilePath, const QString &outputFilePath):
	    m_inputFilePath(inputFilePath), m_outputFilePath(outputFilePath)
	{

	}

	Job::~Job()
	{
		clear();
	}

	void Job::setInputFilePath(const QString &path)
	{
		m_inputFilePath = path;
	}

	QString Job::inputFilePath() const
	{
		return m_inputFilePath;
	}

	void Job::setOutputFilePath(const QString &path)
	{
		m_outputFilePath = path;
	}

	QString Job::outputFilePath() const
	{
		return m_outputFilePath;
	}

	QImage Job::output() const
	{
		return m_image;
	}

	bool Job::run()
	{
		clear();

		QElapsedTimer timer;
		timer.restart();

		if (!open())
			return false;
		m_readingTime = timer.restart();

		if (!parse())
			return false;
		m_parsingTime = timer.restart();

		if (!process())
			return false;
		m_processingTime = timer.restart();

		if (!layout())
			return false;
		m_layingOutTime = timer.restart();

		if (!print())
			return false;
		m_printingTime = timer.restart();

		if (!save())
			return false;
		m_savingTime = timer.restart();

		return true;
	}

	bool Job::isError() const
	{
		return !m_error.isEmpty();
	}

	QString Job::errorMessage() const
	{
		return m_error;
	}

	QStringList Job::warningMessages() const
	{
		return m_warnings;
	}

	int Job::warningMessageCount() const
	{
		return m_warnings.count();
	}

	int Job::inputByteCount() const
	{
		return m_inByteCount;
	}

	qint64 Job::readingTime() const
	{
		return m_readingTime;
	}

	qint64 Job::parsingTime() const
	{
		return m_parsingTime;
	}

	qint64 Job::processingTime() const
	{
		return m_processingTime;
	}

	qint64 Job::layingOutTime() const
	{
		return m_layingOutTime;
	}

	qint64 Job::printingTime() const
	{
		return m_printingTime;
	}

	qint64 Job::savingTime() const
	{
		return m_savingTime;
	}

	int Job::outputByteCount() const
	{
		return m_outByteCount;
	}

	bool Job::open()
	{
		QFile file(m_inputFilePath);

		if (!file.open(QFile::ReadOnly))
		{
			error(QString("While opening: %1").arg(file.errorString()));
			return false;
		}
		if (file.bytesAvailable() > 1024*1024) // 20MB => 1+ millions of objects
		{
			error(QString("While opening: File too big"));
			return false;
		}
		m_data = file.readAll();
		m_inByteCount = m_data.count();
		return true;
	}

	bool Job::parse()
	{
		FileReader reader;
		reader.setData(m_data);
		if (!reader.parse())
		{
			m_data.clear();
			warning(reader.warnings());
			error(QString("While parsing: %1").arg(reader.errorString()));
			return false;
		}

		m_data.clear();
		m_commands = reader.takeCommands();
		warning(reader.warnings());
		return true;
	}

	bool Job::process()
	{
		Processor processor;
		for (auto *command: m_commands)
		{
			command->execute(&processor);
			if (processor.isError())
			{
				qDeleteAll(m_commands);
				m_commands.clear();
				warning(processor.warningMessages());
				error(QString("While processing: %1").arg(processor.errorMessage()));
				return false;
			}
		}
		qDeleteAll(m_commands);
		m_commands.clear();
		m_objects = processor.takeObjects();
		warning(processor.warningMessages());
		return true;
	}

	bool Job::layout()
	{
		m_scene.reset(new GraphicsScene);
		m_scene->setDarkBrush(QBrush(QColor("#2aa198")));
		m_scene->setDarkPen(QPen(Qt::NoPen));
		m_scene->setClearBrush(QBrush(QColor("#002b36")));
		m_scene->setClearPen(QPen(Qt::NoPen));
		for (auto object: m_objects)
		{
			m_scene->addGerberObject(object);
			delete object;
		}
		m_objects.clear();
		return true;
	}

	bool Job::print()
	{
		// Unit is the own file's unit (inch or mm)
		// Margin is in %
		auto boundingRect = m_scene->itemsBoundingRect();
		static const qreal margin = 5.0/100.0;
		boundingRect.setTop(boundingRect.top() - boundingRect.height()*margin);
		boundingRect.setLeft(boundingRect.left() - boundingRect.width()*margin);
		boundingRect.setWidth(boundingRect.width() * (1+2*margin));
		boundingRect.setHeight(boundingRect.height() * (1+2*margin));
		m_scene->setSceneRect(boundingRect);

		static const int pixelPerMm = 100;
		static const int pixelPerInch = int(2.54 * pixelPerMm);
		QSizeF sizeF = boundingRect.size();
		Unit unit = ImperialUnit; // FIXME: need to query for this one
		if (unit == MetricUnit)
			sizeF = sizeF.scaled(pixelPerMm, pixelPerMm, Qt::KeepAspectRatio);
		else
			sizeF = sizeF.scaled(pixelPerInch, pixelPerInch, Qt::KeepAspectRatio);

		m_image = QImage(sizeF.toSize(), QImage::Format_ARGB32);
		m_image.fill(QColor("#2aa198"));
		if (m_image.isNull())
		{
			error(QString("Null image, size=[%1,%2]px=[%3,%4]mm")
			      .arg(m_image.width()).arg(m_image.height())
			      .arg(m_image.widthMM()).arg(m_image.heightMM()));
			return false;
		}
		QPainter painter(&m_image);
		m_scene->render(&painter);
		m_scene.reset();
		return true;
	}


	// BMP, JPG or PNG
	bool Job::save()
	{
		if (!m_image.save(m_outputFilePath))
		{
			error(QString("While saving: %1").arg("Unkown error"));
			return false;
		}
		m_outByteCount = m_image.byteCount();
		m_image = QImage();
		return true;
	}

	void Job::clear()
	{
		m_error.clear();
		m_warnings.clear();

		m_scene.reset();

		qDeleteAll(m_objects);
		m_objects.clear();

		qDeleteAll(m_commands);
		m_commands.clear();

		m_readingTime = 0;
		m_parsingTime = 0;
		m_processingTime = 0;
		m_layingOutTime = 0;
		m_printingTime = 0;
		m_savingTime = 0;

		m_inByteCount = 0;
		m_outByteCount = 0;
	}

	void Job::warning(const QString &text)
	{
		m_warnings.append(text);
	}

	void Job::warning(const QStringList &texts)
	{
		m_warnings.append(texts);
	}

	void Job::error(const QString &text)
	{
		m_error = text;
	}

}